// g++ -g3 -ggdb -O0 -DDEBUG -I/usr/include/cryptopp Driver.cpp -o Driver.exe -lcryptopp -lpthread
// g++ -g -O2 -DNDEBUG -I/usr/include/cryptopp Driver.cpp -o Driver.exe -lcryptopp -lpthread

#include <cryptopp/osrng.h>
using CryptoPP::AutoSeededRandomPool;

#include <iostream>
using std::cout;
using std::cerr;
using std::endl;

#include <string>
using std::string;

#include <cstdlib>
using std::exit;

#include <cryptopp/cryptlib.h>
using CryptoPP::Exception;

#include <cryptopp/hex.h>
using CryptoPP::HexEncoder;
using CryptoPP::HexDecoder;

#include <cryptopp/filters.h>
using CryptoPP::StringSink;
using CryptoPP::StringSource;
using CryptoPP::StreamTransformationFilter;

#include <cryptopp/aes.h>
using CryptoPP::AES;

#include <cryptopp/ccm.h>
using CryptoPP::CBC_Mode;

#include <assert.h>

int main(int argc, char* argv[])
{
  AutoSeededRandomPool prng;

  //byte * key = new byte[AES::DEFAULT_KEYLENGTH];
  byte key[AES::DEFAULT_KEYLENGTH];
/*  MyClass * p1 = new MyClass[5];*/

  prng.GenerateBlock(key, sizeof(key));

  byte iv[AES::BLOCKSIZE];
  prng.GenerateBlock(iv, sizeof(iv));

  string plain = "Yo dawg, I heard you like encryption!";
  string cipher, encoded, recovered;

  /*********************************\
  \*********************************/

  // Pretty print key
  encoded.clear();
  StringSource(key, sizeof(key), true,
    new HexEncoder(
      new StringSink(encoded)
    ) // HexEncoder
  ); // StringSource
  cout << "key: " << encoded << endl;

  // Pretty print iv
  encoded.clear();
  StringSource(iv, sizeof(iv), true,
    new HexEncoder(
      new StringSink(encoded)
    ) // HexEncoder
  ); // StringSource
  cout << "iv: " << encoded << endl;

  /*********************************\
  \*********************************/

  try
  {
    cout << "plain text: " << plain << endl;

    CBC_Mode< AES >::Encryption e;
    e.SetKeyWithIV(key, sizeof(key), iv);

    // The StreamTransformationFilter removes
    //  padding as required.
    StringSource s(plain, true, 
      new StreamTransformationFilter(e,
        new StringSink(cipher)
      ) // StreamTransformationFilter
    ); // StringSource

#if 0
    StreamTransformationFilter filter(e);
    filter.Put((const byte*)plain.data(), plain.size());
    filter.MessageEnd();

    const size_t ret = filter.MaxRetrievable();
    cipher.resize(ret);
    filter.Get((byte*)cipher.data(), cipher.size());
#endif
  }
  catch(const CryptoPP::Exception& e)
  {
    cerr << e.what() << endl;
    exit(1);
  }

  /*********************************\
  \*********************************/

  // Pretty print
  encoded.clear();
  StringSource(cipher, true,
    new HexEncoder(
      new StringSink(encoded)
    ) // HexEncoder
  ); // StringSource
  cout << "cipher text: " << encoded << endl;

  /*********************************\
  \*********************************/

  try
  {
    CBC_Mode< AES >::Decryption d;
    d.SetKeyWithIV(key, sizeof(key), iv);

    // The StreamTransformationFilter removes
    //  padding as required.
    StringSource s(cipher, true, 
      new StreamTransformationFilter(d,
        new StringSink(recovered)
      ) // StreamTransformationFilter
    ); // StringSource

#if 0
    StreamTransformationFilter filter(d);
    filter.Put((const byte*)cipher.data(), cipher.size());
    filter.MessageEnd();

    const size_t ret = filter.MaxRetrievable();
    recovered.resize(ret);
    filter.Get((byte*)recovered.data(), recovered.size());
#endif

    cout << "recovered text: " << recovered << endl;
  }
  catch(const CryptoPP::Exception& e)
  {
    cerr << e.what() << endl;
    exit(1);
  }

  /*********************************\
  \*********************************/

  return 0;
}

